﻿//-----------------------------------------------------------------------
// <copyright file="XMLDeserialiser.cs" company="Microsoft Corporation">
//     Copyright Microsoft Corporation. All Rights Reserved. This code released under the terms of the Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) This is sample code only, do not use in production environments.
// </copyright>
//-----------------------------------------------------------------------
namespace VisualStudioOnlineOData
{
    using System;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
    using System.Xml.Linq;

    /// <summary>
    /// Custom deserialiser for us
    /// </summary>
    public static class XMLDeserialiser
    {
        /// <summary>
        /// Deserialises from string.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="content">The content.</param>
        /// <returns></returns>
        public static T DeserialiseFromString<T>(string content)
           where T : new()
        {
            if (content == null)
            {
                return default(T);
            }

            var xDocument = XDocument.Parse(content);
            return Deserialise<T>(xDocument);
        }

        /// <summary>
        /// Deserialises from xdocument.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="xdocument">The xdocument.</param>
        /// <returns></returns>
        private static T Deserialise<T>(XDocument xdocument)
            where T : new()
        {
            var result = new T();
            var node = xdocument.Root;
            ParseProperty(typeof(T), node, result);
            return result;
        }

        /// <summary>
        /// Gets the value.
        /// </summary>
        /// <param name="nodeValue">The node value.</param>
        /// <param name="element">The element.</param>
        /// <param name="property">The property.</param>
        /// <returns></returns>
        private static object GetValue(string nodeValue, XElement element, PropertyInfo property)
        {
            var parserCulture = CultureInfo.InvariantCulture;
            if (property.PropertyType == typeof(bool))
            {
                return bool.Parse(nodeValue);
            }

            if (property.PropertyType == typeof(DateTime))
            {
                return DateTime.Parse(nodeValue, parserCulture);
            }

            if (property.PropertyType == typeof(int))
            {
                return int.Parse(nodeValue, parserCulture);
            }

            if (property.PropertyType == typeof(float))
            {
                return float.Parse(nodeValue, parserCulture);
            }

            if (property.PropertyType != typeof(string) && typeof(System.Collections.IEnumerable).GetTypeInfo().IsAssignableFrom(property.PropertyType.GetTypeInfo()))
            {
                var childElements = element.Elements().Where(_ => _.Name.LocalName.Equals(property.Name, StringComparison.CurrentCultureIgnoreCase)).ToArray();
                if (!childElements.Any() && element.Name.LocalName.Equals(property.Name, StringComparison.CurrentCultureIgnoreCase))
                {
                    childElements = new XElement[1] { element };
                }

                var array = Array.CreateInstance(property.PropertyType.GenericTypeArguments[0], childElements.Length);
                for (int counter = 0; counter < childElements.Length; counter++)
                {
                    var newTarget = Activator.CreateInstance(property.PropertyType.GenericTypeArguments[0]);
                    ParseProperty(property.PropertyType.GenericTypeArguments[0], childElements[counter], newTarget);
                    array.SetValue(newTarget, counter);
                }

                return array;
            }

            if (property.PropertyType != typeof(string) && property.PropertyType.GetTypeInfo().IsClass)
            {
                var newTarget = Activator.CreateInstance(property.PropertyType);
                ParseProperty(property.PropertyType, element, newTarget);

                return newTarget;
            }

            return nodeValue;
        }

        /// <summary>
        /// Parses the property.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <param name="node">The node.</param>
        /// <param name="target">The target.</param>
        private static void ParseProperty(Type type, XElement node, object target)
        {
            var nodeValue = "";
            XElement element;
            XAttribute attribute;
            bool found;

            foreach (var property in type.GetRuntimeProperties())
            {
                found = false;
                nodeValue = "";
                element = null;

                attribute = node.Attributes().SingleOrDefault(_ => _.Name.LocalName.Equals(property.Name, StringComparison.CurrentCultureIgnoreCase));
                if (attribute == null)
                {
                    var elements = node.Elements().Where(_ => _.Name.LocalName.Equals(property.Name, StringComparison.CurrentCultureIgnoreCase));
                    if (elements.Any())
                    {
                        if (elements.Count() > 1)
                        {
                            found = true;
                            element = node;
                        }
                        else
                        {
                            element = elements.First();
                            if (element == null)
                            {
                                if (property.Name == "Value")
                                {
                                    found = true;
                                    nodeValue = node.Value;
                                }
                            }
                            else
                            {
                                found = true;
                                nodeValue = element.Value;
                            }
                        }
                    }
                }
                else
                {
                    found = true;
                    nodeValue = attribute.Value;
                }

                if (!found)
                {
                    continue;
                }

                object propertyValue = GetValue(nodeValue, element, property);

                property.SetValue(target, propertyValue);
            }
        }
    }
}